/**
 * 定时器管理
 */
export const timerMgr = _ as TimerMgr;
export class TimerMgr {

    private _pool: ITimer[] = [];
    private _timers: ITimer[] = [];
    private _timerDic: { [id: number]: ITimer } = {};
    private _callerId = 0;
    private _callbackId = 0;
    private _curFrame = 0;

    init() {
        (timerMgr as any) = this;
    }

    /**
     * 延迟执行 相同caller的相同callback会覆盖
     * @param caller 
     * @param callback 
     * @param args 
     */
    callLater(caller: any, callback: Function, args?: any[]) {
        this.create(false, caller, callback, 0, args);
    }

    /**
     * 延迟delay秒后执行 相同caller的相同callback会覆盖
     * @param caller 
     * @param callback 
     * @param delay 
     * @param args 
     */
    once(caller: any, callback: Function, delay: number, args?: any[]) {
        this.create(false, caller, callback, delay, args);
    }

    /**
     * 延迟delay帧后执行 相同caller的相同callback会覆盖
     * @param caller 
     * @param callback 
     * @param delay 
     * @param args 
     */
    frameOnce(caller: any, callback: Function, delay: number, args?: any[]) {
        this.create(true, caller, callback, delay, args);
    }

    /**
     * 立即执行定时器
     * @param caller 
     * @param callback 
     */
    callTimer(caller: any, callback: Function) {
        let timer = this._timerDic[this.getId(caller, callback)];
        if (timer) {
            timer.needRecy = true;
            delete this._timerDic[timer.id];
            timer.callback.apply(timer.caller, timer.args);
        }
    }

    /**
     * 清除caller的定时器
     * @param caller 
     * @param callback 
     */
    clear(caller: any, callback: Function) {
        let timer = this._timerDic[this.getId(caller, callback)];
        if (timer) {
            timer.needRecy = true;
            delete this._timerDic[timer.id];
        }
    }

    /**
     * 清除caller所有定时器
     * @param caller 
     */
    clearAll(caller: any) {
        for (let i = this._timers.length; --i >= 0;) {
            let timer = this._timers[i];
            if (timer.caller === caller) {
                timer.needRecy = true;
                delete this._timerDic[timer.id];
            }
        }
    }

    onUpdate() {
        let now = Date.now() / 1000;
        ++this._curFrame;

        let recyCount = 0;
        for (let i = 0; i < this._timers.length; ++i) {
            let timer = this._timers[i];
            if (timer.needRecy) {
                ++recyCount;
                continue;
            }
            if (timer.callSecond ? now >= timer.callSecond : this._curFrame >= timer.callFrame) {
                timer.needRecy = true;
                delete this._timerDic[timer.id];
                timer.callback.apply(timer.caller, timer.args);
            }
        }

        if (recyCount && (recyCount > 30 || this._curFrame % 300 == 0)) {
            let timers: ITimer[] = [];
            for (let i = 0; i < this._timers.length; ++i) {
                let timer = this._timers[i];
                if (timer.needRecy) {
                    timer.needRecy = false;
                    timer.caller = null;
                    timer.callback = null;
                    timer.args = null;
                    this._pool.push(timer);
                    continue;
                }
                timers.push(timer);
            }
            this._timers = timers;
        }
    }

    private getId(caller: any, callback: Function) {
        let id1 = caller["_$CID"] || (caller["_$CID"] = ++this._callerId);
        let id2 = callback["_$CID"] || (callback["_$CID"] = ++this._callbackId);
        return id1 + id2 * 100000;
    }

    private create(useFrame: boolean, caller: any, callback: Function, delay: number, args: any[]) {
        let id = this.getId(caller, callback);
        let timer = this._timerDic[id] || this._pool.pop() || {} as ITimer;
        timer.id = id;
        timer.caller = caller;
        timer.callback = callback;
        timer.callSecond = useFrame ? 0 : Date.now() / 1000 + delay;
        timer.callFrame = useFrame ? this._curFrame + delay : 0;
        timer.args = args;
        if (!this._timerDic[id]) {
            this._timers.push(timer);
            this._timerDic[timer.id] = timer;
        }
    }

}

interface ITimer {
    id: number,
    caller: any,
    callback: Function,
    callSecond: number,
    callFrame: number,
    args: any[],
    needRecy: boolean,
}